/*
 *	socketcomm.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include "sockcomADT.h"
#include "sockets.h"
#include "clsv.h"

#define QLEN		64

#define	MAX(x, y)	((x) > (y) ? (x) : (y))

struct CommCDT {
  int msock; 
  int ssock;
  int usock;
  int nfds;
  fd_set rfds;
  commType type; 
  struct sockaddr_in addr;
};

static int read_data(Comm comm, void *p, int qty, int service);

Comm 
initComm(commType type, char * host, char * port )
{
    Comm comm = calloc(1, sizeof(struct CommCDT));
    if (comm != NULL)
	{
        comm->msock = comm->ssock = -1;
        if (type == COMM_CLIENT)
        {
            comm->ssock = connectTCP(host, port);
            comm->usock = connectUDP(host, port);
        }
		else
		{
            comm->msock = passiveTCP(port, QLEN);
            comm->usock = passiveUDP(port);
            comm->nfds = MAX(comm->msock, comm->usock) + 1;
            FD_ZERO(&(comm->rfds));
		}
        comm->type = type;
    }

    return comm;
}

int
sockSel(Comm comm)
{
		FD_SET(comm->msock, &(comm->rfds));
		FD_SET(comm->usock, &(comm->rfds));

		if( select(comm->nfds, &(comm->rfds), (fd_set *)0, (fd_set *)0,
				(struct timeval *)0) < 0 )
			return 0;
		if( FD_ISSET(comm->msock, &(comm->rfds)) )
			return 1;

		if( FD_ISSET(comm->usock, &(comm->rfds)) )
			return 2;
		return -1;
}

void 
send_packet(Comm comm, const void * p, int qty, int service)
{
    int aux, sock;
	void * packet, * p2;
	char * offset;

	if( service == _TCP_ )
		sock= comm->ssock;
	else
		sock= comm->usock;
	
	/* backup p */
	if ((p2 = malloc(qty)) == NULL)
	{
		fprintf(stderr, "Memory allocation error\n");
		return;
	}
	memcpy(p2, p, qty);
	
	/* make space for qty to send */
	aux = htonl(qty);
	if ((packet = malloc(qty+sizeof(aux))) == NULL)
	{
		fprintf(stderr, "Memory allocation error\n");
		return;
	}
	memcpy(packet, &aux, sizeof(aux));
	offset = (char *)packet+sizeof(aux);
	memcpy((void *)offset, p2, qty);
	if( service == _TCP_ )
    	write(sock, packet, qty+sizeof(aux));
    else
    	write(sock, (char *)packet+sizeof(aux), qty);
	
	free(p2);
	free(packet);
    return;
}

int 
receive_packet(Comm comm, void *p, int lim, int service )
{
    int qty, aux;
    int ret;

	if( service == _TCP_ )
    {
    	ret = read_data(comm, &aux, sizeof(aux), service);
		qty = ntohl(aux);
	    if (qty > lim || !ret)
			return 0;
	
	    ret = read_data(comm, p, qty, service);

    	if (qty <= lim && ret)
			return qty;
		else
			return 0;
    }
    else
    	read_data(comm, p, lim, service);
    return 1;
}

int 
receive_UDP(Comm comm, void *p, int lim )
{
    int qty, ret, alen= sizeof(struct sockaddr_in);

	if ((ret= recvfrom(comm->usock, p, lim, 0, 
			(struct sockaddr *)&(comm->addr), (socklen_t *)&alen)) < 0)
	{
		fprintf(stderr, "recvfrom: error reading UDP datagram.\n");
		return 0;
	}
	return 1;
}

void
send_UDP(Comm comm, void * stream, int lim)
{
	int alen;

	sendto(comm->usock, stream, lim, 0, (struct sockaddr *)&(comm->addr), 
				sizeof(struct sockaddr_in));
	return;
}

static int
read_data(Comm comm, void *p, int qty, int service)
{
    int current, n, tot = 0;

	if( service == _TCP_ )
	{
	    for (current = 0; current < qty; current += n)
		{
   	    	n = read(comm->ssock, p, qty - current);
			tot += n;
   	    	if(n < 0)
			{
				fprintf(stderr, "Error reading data\n");
				exit(1);
			}
   	    	if (n == 0 && current != qty)
				return 0;
    	}
	}
	else
		read(comm->usock, p, qty);
	return 1;
}

void 
closeComm(Comm comm, sockType sock) 
{
	int aux= 0;

	if ((sock == SERVER || sock == BOTH) && comm->msock != -1)
	{
		close(comm->msock);
		comm->msock = -1;
		close(comm->usock);
		comm->usock = -1;
    }
    if ((sock == CLIENT || sock == BOTH) && comm->ssock != -1 )
    {
        close(comm->ssock);
        comm->ssock = -1;
    }
}

int acceptComm(Comm comm) 
{
    struct  sockaddr_in fsin;   /* the address of a client  */
    unsigned int    alen;

    if (comm->type == COMM_SERVER) 
    {
        comm->ssock = accept(comm->msock, (struct sockaddr *)&fsin, &alen);
    }

    return comm->ssock;
}

char *
printIP(Comm comm)
{
	socklen_t fromlen;

	fromlen= sizeof(struct sockaddr_in);
	getpeername(comm->ssock, (struct sockaddr *)&(comm->addr), &fromlen);
	return inet_ntoa(comm->addr.sin_addr);
}
